#!/usr/bin/env python
# -*-coding:utf-8 -*-
# Project_name:KmExportsToExcel
# File_name:km_exports_to_excel_window.py
# Author:chen chang song
# Time:2025年04月13日

import os
import time
import json
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from openpyxl import Workbook
from openpyxl.styles import Alignment, PatternFill
from openpyxl.formatting.rule import CellIsRule
from openpyxl.worksheet.datavalidation import DataValidation


class KmToExcelConverterGUI:
    def __init__(self, root):
        """初始化GUI界面"""
        self.root = root
        self.root.title("km转Excel工具_V20250412")
        self.mind_path = ""  # 存储选择的.km文件路径

        # ============== 主界面布局 ==============
        self.main_frame = ttk.Frame(root, padding="10")
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 标题栏（包含帮助按钮）
        self.header_frame = ttk.Frame(self.main_frame)
        self.header_frame.pack(fill=tk.X)

        # 帮助按钮
        self.help_btn = ttk.Button(
            self.header_frame,
            text="帮助?",
            width=6,
            command=self.show_help
        )
        self.help_btn.pack(side=tk.RIGHT, padx=5)

        # ============== 文件路径选择区域 ==============
        self.path_frame = ttk.LabelFrame(self.main_frame, text=".km文件路径：", padding="10")
        self.path_frame.pack(fill=tk.X, pady=5)

        # 路径显示标签
        self.path_label = ttk.Label(self.path_frame, text="未选择路径")
        self.path_label.pack(side=tk.LEFT, fill=tk.X, expand=True)

        # 选择路径按钮
        self.select_btn = ttk.Button(
            self.path_frame,
            text="选择.km文件路径",
            command=self.select_directory
        )
        self.select_btn.pack(side=tk.LEFT, padx=5)

        # 开始转换按钮（初始状态为禁用）
        self.convert_btn = ttk.Button(
            self.path_frame,
            text="开始转换",
            state=tk.DISABLED,
            command=self.start_conversion
        )
        self.convert_btn.pack(side=tk.LEFT)

        # ============== 转换结果显示区域 ==============
        self.result_frame = ttk.LabelFrame(self.main_frame, text="转换结果:", padding="10")
        self.result_frame.pack(fill=tk.BOTH, expand=True, pady=5)

        # 结果文本框（只读状态）
        self.result_text = tk.Text(
            self.result_frame,
            height=7,
            wrap=tk.WORD,
            state=tk.DISABLED
        )
        self.result_text.pack(fill=tk.BOTH, expand=True)

        # ============== 状态栏 ==============
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(
            root,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W
        )
        self.status_bar.pack(fill=tk.X)
        self.status_var.set("就绪")  # 初始状态

    def select_directory(self):
        """打开目录选择对话框，获取.km文件所在目录"""
        path = filedialog.askdirectory(title="选择.km文件所在目录")
        if path:
            self.mind_path = path
            self.path_label.config(text=path)
            self.convert_btn.config(state=tk.NORMAL)  # 启用转换按钮
            self.status_var.set(f"已选择目录: {path}")

    def start_conversion(self):
        """执行转换操作的主函数"""
        if not self.mind_path:
            messagebox.showerror("错误", "请先选择.km文件路径")
            return

        self.status_var.set("正在转换中...")
        self.root.update()  # 强制更新界面，显示状态变化

        try:
            # 创建转换器实例并执行转换
            converter = KmToExcelConverter(self.mind_path)
            errors_list, test_cases, excel_files = converter.all_km_path_list()

            # 根据转换结果构建显示消息
            if errors_list:  # 存在错误情况
                result_msg = f"转换失败:\n"
                for error in errors_list:
                    result_msg += f"{error}\n\n"
                result_msg += f"\n\n请到 {self.mind_path} 路径下检查km文件的标签！"
                self.status_var.set("转换失败")
            else:  # 成功情况
                result_msg = f"共转换 {len(test_cases)} 个.km文件\n\n生成的Excel文件:\n"
                result_msg += "\n".join(f"• {file}" for file in excel_files)
                result_msg += f"\n\n请到 {self.mind_path} 路径下查看！"
                self.status_var.set("转换完成")

            self.update_result(result_msg)

        except Exception as e:
            messagebox.showerror("转换错误", f"转换过程中出现错误:\n{str(e)}")
            self.status_var.set("转换失败")

    def update_result(self, message):
        """更新结果文本框内容"""
        self.result_text.config(state=tk.NORMAL)
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, message)
        self.result_text.config(state=tk.DISABLED)

    def show_help(self):
        """显示帮助信息对话框"""
        help_msg = "如需帮助请联系昌松"
        messagebox.showinfo("帮助", help_msg)


class KmToExcelConverter:
    def __init__(self, mind_path):
        """初始化转换器
        :param mind_path: .km文件存放目录路径
        """
        self.mind_path = mind_path

    def all_km_path_list(self):
        """遍历目录处理所有.km文件
        :return: (错误列表, 测试用例数据列表, 生成的Excel文件列表)
        """
        file_name_list = []
        km_data_list = []
        excel_files = []
        errors_list = []  # 收集所有错误信息

        # 遍历目录中的.km文件
        for filename in os.listdir(self.mind_path):
            if filename.endswith('.km'):
                file_name_list.append(filename)
                km_path = os.path.join(self.mind_path, filename)

                # 解析.km文件内容
                errors, km_data = self.extract_km_content(km_path)
                if errors:
                    errors_list.extend(errors)  # 收集错误信息

                km_data_list.append(km_data)

                # 将数据写入Excel
                excel_file = self.test_case_write_to_excel(km_path, km_data)
                excel_files.append(excel_file)

        return errors_list, km_data_list, excel_files

    def extract_km_content(self, km_path):
        """解析.km文件内容
        :param km_path: .km文件路径
        :return: (错误列表, 结构化测试用例数据)
        数据结构示例：
        [
            ['需求ID', '用例等级', '用例目录', '用例名称', '前置条件', '用例步骤', '预期结果'],
            ...
        ]
        """
        with open(km_path, 'r', encoding='utf-8') as f:
            data = f.read()
        data = json.loads(data)
        root = data['root']

        # 从根节点提取需求ID
        root_text = root['data']['text']
        parts = root_text.split('\n')
        requirement_id = parts[0].split('：')[1].strip()

        result = []
        allowed_resources = {"用例目录", "用例名称", "用例步骤", "预期结果", "前置条件"}
        errors = []

        def process_node(node, current_path, parent_valid):
            """递归处理节点树"""
            data = node.get('data', {})
            node_text = data.get('text', '')
            resources = data.get('resource', [])
            children = node.get('children', [])

            # 检查节点是否合法
            current_valid = isinstance(resources, list) and any(r in allowed_resources for r in resources)

            if not current_valid and parent_valid:
                path = '-'.join(current_path)
                errors.append(f"{km_path}，'{path}' 下一个节点，存在未打标签的数据，请改正后重新转换！")
                return

            # 构建功能路径
            new_path = current_path.copy()
            if '用例目录' in resources:
                new_path.append(node_text)

            # 处理测试用例节点
            if '用例名称' in resources:
                path_str = '-'.join(new_path)
                priority = data.get('priority')
                priority_str = '中'
                if priority == 1:
                    priority_str = '高'
                elif priority == 3:
                    priority_str = '低'

                # 提取用例步骤和预期结果
                for child in children:
                    pre_condition, step, expected = '', '', ''
                    child_data = child.get('data', {})
                    child_resources = child_data.get('resource', [])

                    if '前置条件' in child_resources:
                        pre_condition = child_data.get('text', '')
                        # 查找步骤和预期结果
                        for step_child in child.get('children', []):
                            step_data = step_child.get('data', {})
                            if '用例步骤' in step_data.get('resource', []):
                                step = step_data.get('text', '')
                                # 查找预期结果
                                for expected_child in step_child.get('children', []):
                                    expected_data = expected_child.get('data', {})
                                    if '预期结果' in expected_data.get('resource', []):
                                        expected = expected_data.get('text', '')
                                        break
                                break
                    elif '用例步骤' in child_resources:
                        step = child_data.get('text', '')
                        # 查找预期结果
                        for expected_child in child.get('children', []):
                            expected_data = expected_child.get('data', {})
                            if '预期结果' in expected_data.get('resource', []):
                                expected = expected_data.get('text', '')
                                break

                    if step and expected:
                        result.append([
                            requirement_id,
                            priority_str,
                            path_str,
                            data['text'],
                            pre_condition,
                            step,
                            expected
                        ])

            # 递归处理子节点
            for child in children:
                process_node(child, new_path, current_valid)

        # 开始处理根节点的子节点
        for child in root.get('children', []):
            process_node(child, [], False)

        return errors, result

    def test_case_write_to_excel(self, km_path, row_data_list):
        """将测试用例数据写入Excel文件
        :param km_path: 原始.km文件路径
        :param row_data_list: 测试用例数据列表
        :return: 生成的Excel文件名
        """
        # 准备文件名和路径
        file_dir, file_name = os.path.split(km_path)
        file_base = os.path.splitext(file_name)[0]

        # 创建工作簿和工作表
        wb = Workbook()
        ws = wb.active
        ws.title = file_base[:31]  # Excel工作表名称最大31个字符

        # ============== 表头设置 ==============
        headers = ['需求ID', '用例等级', '用例目录', '用例名称', '前置条件', '用例步骤', '预期结果', '执行结果', '备注']
        ws.append(headers)

        # 设置自动筛选
        ws.auto_filter.ref = "A1:I1"

        # ============== 列宽设置 ==============
        column_widths = {
            'A': 7,  # 需求ID
            'B': 9,  # 用例等级
            'C': 50,  # 用例目录
            'D': 40,  # 用例名称
            'E': 30,  # 前置条件
            'F': 50,  # 用例步骤
            'G': 30,  # 预期结果
            'H': 12,  # 执行结果
            'I': 25  # 备注
        }
        for col, width in column_widths.items():
            ws.column_dimensions[col].width = width

        # ============== 数据验证设置 ==============
        status_options = ["通过", "失败", "阻塞", "未执行", "待确认", "用例待更新", "用例废弃"]
        dv = DataValidation(
            type="list",
            formula1=f'"{",".join(status_options)}"',
            allow_blank=True
        )
        ws.add_data_validation(dv)
        dv.add(f"H2:H{len(row_data_list) + 1}")  # 应用到执行结果列

        # ============== 条件格式设置 ==============
        status_colors = {
            "通过": '00FF00',  # 绿色
            "失败": 'FF7F00',  # 橙色
            "阻塞": 'FF0000',  # 红色
            "未执行": 'FFFF00',  # 黄色
            "待确认": 'FFFF00',
            "用例待更新": 'FFFF00',
            "用例废弃": 'FFFF00'
        }
        for status, color in status_colors.items():
            rule = CellIsRule(
                operator='equal',
                formula=[f'"{status}"'],
                fill=PatternFill(
                    start_color=color,
                    end_color=color,
                    fill_type='solid'
                )
            )
            ws.conditional_formatting.add(f'H2:H{len(row_data_list) + 1}', rule)

        # ============== 写入测试数据 ==============
        for case_data in row_data_list:
            # 补充空列（执行结果和备注）
            full_row = case_data + ['', '']
            ws.append(full_row)

        # ============== 格式优化 ==============
        # 冻结首行
        ws.freeze_panes = 'A2'

        # 设置自动换行和顶部对齐
        for row in ws.iter_rows():
            for cell in row:
                cell.alignment = Alignment(
                    wrap_text=True,
                    vertical='top'
                )

        # 设置行高
        ws.row_dimensions[1].height = 20
        for row_idx in range(2, ws.max_row + 1):
            ws.row_dimensions[row_idx].height = 32

        # ============== 保存文件 ==============
        timestamp = time.strftime("%Y%m%d%H%M%S")
        output_filename = f"{file_base}_{timestamp}.xlsx"
        output_path = os.path.join(file_dir, output_filename)
        wb.save(output_path)

        return output_filename


if __name__ == "__main__":
    root = tk.Tk()
    app = KmToExcelConverterGUI(root)
    root.mainloop()